using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using XnaPathfidingLibrary;

namespace GemTowerDefence
{
  public class Pathfinder
  {
    private readonly Player _player;
    private readonly Game _game;
    private readonly AStarSearch _search;
    private readonly Map _pathMap;
    private Texture2D _navigatable;
    private Texture2D _nonNavigatable;
    private Texture2D _start;
    private Texture2D _end;
    private Texture2D _char;
    private Texture2D _visited;

    private bool _isUpdating;
    private KeyboardState _prevKeyState;
    private bool _drawPathFinding;
    private Queue<Vector2> _allWaypoints = new Queue<Vector2>();
    private Queue<Vector2> _resultPath = new Queue<Vector2>();
    private Vector2 _currentEnd;

    public Pathfinder(Game game)
    {
      _game = game;
      _player = _game.Player;
      _pathMap = new Map(_player.Map.Height, _player.Map.Width);
      _search = new AStarSearch(game) { Map = _pathMap };
      // Time limit in milliseconds. This says everytime the search's Update method is called, it will have 0.01 milliseconds
      // of processing time until it needs to return and let the game go on. You can adjust this time to give more or less
      // time for the search, each frame. Remember that at least one node you be visited per frame, even if it takes more than
      // the time limit.
      _search.TimeLimit = 0.01;
      // Amount of iterations (visited nodes) the search algorithm can take each frame. 
      // It works only if Configuration is SearchConfiguration.IterationLimited.
      _search.IterationLimit = 25;
      // Defines that the search will be timed, as explained before. You can change it for NotTimed or IterationLimited.
      _search.Configuration = SearchConfiguration.NotTimed;
      // Adds the search component.
      _game.Components.Add(_search);

//      var birthPt = _player.Map.HaveToGoPonts[0];
//      var firstPt = _player.Map.HaveToGoPonts[1];
//      _pathMap.StartNode = _pathMap.Nodes[(int)birthPt.Y, (int)birthPt.X];
//      _pathMap.EndNode = _pathMap.Nodes[(int)firstPt.Y, (int)firstPt.X];
      InitializeWaypoints();
    }

    private void InitializeWaypoints()
    {
      _resultPath.Clear();
      _allWaypoints.Clear();
      foreach (var haveToGoPont in _player.Map.HaveToGoPonts)
        _allWaypoints.Enqueue(haveToGoPont);
      var start = _allWaypoints.Dequeue();
      _pathMap.StartNode = _pathMap.Nodes[(int)start.Y, (int)start.X];
      var end = _allWaypoints.Dequeue();
      _currentEnd = end;
      _pathMap.EndNode = _pathMap.Nodes[(int)end.Y, (int)end.X];
    }

    public void LoadContent()
    {
      _navigatable = _game.Content.Load<Texture2D>(@"Textures\pathfinder\navigatable");
      _nonNavigatable = _game.Content.Load<Texture2D>(@"Textures\pathfinder\nonnavigatable");
      _visited = _game.Content.Load<Texture2D>(@"Textures\pathfinder\visited");
      _start = _game.Content.Load<Texture2D>(@"Textures\pathfinder\start");
      _end = _game.Content.Load<Texture2D>(@"Textures\pathfinder\end");
      _char = _game.Content.Load<Texture2D>(@"Textures\pathfinder\character");
    }

    public void Update(GameTime gameTime)
    {
      var keyState = Keyboard.GetState();
      if (keyState.IsKeyUp(Keys.H) && _prevKeyState.IsKeyDown(Keys.H))
        _drawPathFinding = !_drawPathFinding;
      _prevKeyState = keyState;

      if (_player.IsAddingTower && !_isUpdating)
      {
        _game.WaveManager.IsUpdatingPath = true;
        _search.Reset();
        InitializeWaypoints();
        _isUpdating = true;
        UpdateMapState();
      }
       if (_game.WaveManager.IsUpdatingPath && !_isUpdating)
      {
        _resultPath.Clear();
        InitializeWaypoints();
        _isUpdating = true;
        UpdateMapState();
      }
      if (_search.State == SearchState.FinishedFound && _game.WaveManager.IsUpdatingPath)
      {
        foreach (var node in _search.Path)
        {
          var posX = node.Position.X * Game.TextureSize;
          var posY = node.Position.Y * Game.TextureSize;
//          _player.Map.Waypoints.Enqueue(new Vector2(posX, posY));
          _resultPath.Enqueue(new Vector2(posX, posY));
        }
        if (_allWaypoints.Count > 0)
        {
          _pathMap.StartNode = _pathMap.Nodes[(int)_currentEnd.Y, (int)_currentEnd.X];
          var newEnd = _allWaypoints.Dequeue();
          _pathMap.EndNode = _pathMap.Nodes[(int)newEnd.Y, (int)newEnd.X];
          _currentEnd = newEnd;
          _search.Start();
        }
        else
        {
          _player.IsAddingTower = false;
          _player.AllowAddTower = true;
          _player.Map.Waypoints.Clear();
          foreach (var vector2 in _resultPath)
            _player.Map.Waypoints.Enqueue(vector2);
          _game.WaveManager.IsUpdatingPath = false;
          _isUpdating = false;
        }
      }
      else if (_search.State == SearchState.FinishedNotFound)
      {
        if (_player.IsAddingTower)
          _player.AllowAddTower = false;
        _game.WaveManager.IsUpdatingPath = false;
        _isUpdating = false;
      }
    }

    public void UpdateMapState()
    {
      var tileMap = _player.Map;
      for (int r = 0; r < tileMap.Rows.Count; r++)
      {
        var row = tileMap.Rows[r];
        for (int c = 0; c < row.Columns.Count; c++)
        {
          _pathMap.Nodes[r, c].Navigable = _player.IsCellNavigatabe(c, r);
        }
      }
      if (_search.State == SearchState.NotStarted || _search.State == SearchState.FinishedFound)
      {
        _search.Start();
      }
    }

    public void Draw(SpriteBatch spriteBatch)
    {
      if (!_drawPathFinding) return;
      
      // Draw the map.
      // Important: this is not a map library, the map here is used only as search space.
      // This is why no default draw method is provided to the map. If you use you own map format, draw it as you will.
      // This method just draw different textures for each node if it's navigable or not. Visited nodes are tinted gray.
      for (int i = 0; i < _pathMap.Nodes.GetLength(0); i++)
      {
        for (int j = 0; j < _pathMap.Nodes.GetLength(1); j++)
        {
          MapNode n = _pathMap.Nodes[i, j];

          if (n.Navigable)
          {
            if (!n.Visited)
              spriteBatch.Draw(_navigatable, new Vector2(j*Game.TextureSize, i*Game.TextureSize), Color.White);
            else
              spriteBatch.Draw(_visited, new Vector2(j*Game.TextureSize, i*Game.TextureSize), Color.Gray);
          }
          else
          {
            spriteBatch.Draw(_nonNavigatable, new Vector2(j * Game.TextureSize, i * Game.TextureSize), Color.White);
          }
        }
      }

      // If search is finished and the goal was found, draw the path.
      if (_search.State == SearchState.FinishedFound || _search.State == SearchState.FinishedNotFound)
      {
        foreach (var wp in _player.Map.Waypoints)
        {
          spriteBatch.Draw(Game.SelectionTexture, new Vector2(wp.X, wp.Y), Color.Red);
        }
      }

      // Draw the starting point.
      var startPos = _pathMap.StartNode.Position;
      spriteBatch.Draw(_start, new Vector2(startPos.X * Game.TextureSize, startPos.Y * Game.TextureSize), Color.White);

      // Draw the ending point.
      var endPost = _pathMap.EndNode.Position;
      spriteBatch.Draw(_end, new Vector2(endPost.X * Game.TextureSize, endPost.Y * Game.TextureSize), Color.White);

      // Draw the current node being visited.
      if (_search.CurrentNode != null)
      {
        var currentPos = _search.CurrentNode.Position;
        spriteBatch.Draw(_char, new Vector2(currentPos.X * Game.TextureSize, currentPos.Y * Game.TextureSize), Color.White);
      }
    }
  }
}